#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime, timezone
from io import StringIO
from logging import basicConfig, error, exception, info, INFO
from os import unlink
from re import sub
from ssl import create_default_context, SSLContext
from subprocess import run, CompletedProcess
from tempfile import NamedTemporaryFile
from typing import Dict, List, Union
from urllib.error import URLError
from urllib.request import urlopen

from certifi import where
from fastapi import FastAPI, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import PlainTextResponse
from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_500_INTERNAL_SERVER_ERROR, HTTP_502_BAD_GATEWAY, HTTP_503_SERVICE_UNAVAILABLE

from config import Config
from gsapi import GS, GSError, Revision

basicConfig(
	format='%(asctime)s %(levelname)s %(message)s',
	#filename=argv[0].rsplit('.', 1)[0] + '.log',
	level=INFO
)

START: datetime = datetime.now(timezone.utc)
JOBS: Dict[str, int] = {
	'total': 0,
	'success': 0,
	'failure': 0
}

CONTEXT: SSLContext = create_default_context()
CONTEXT.load_verify_locations(cafile=where())

app: FastAPI = FastAPI()
app.add_middleware(
	CORSMiddleware,
	allow_origins=['*'],
	allow_credentials=True,
	allow_methods=['*'],
	allow_headers=['*']
)

@app.get('/ping', response_class=PlainTextResponse)
async def ping():
	return 'pong'

@app.get('/info')
async def information():
	with Config() as config:
		dll: str = config.get('Ghostscript', 'dll')
		ghostscript = {
			'dll': dll,
			'product': None,
			'revision': None,
			'revisiondate': None,
			'copyright': None
		}
		if dll:
			try:
				with GS() as gs:
					revision: Revision = gs.revision()
					ghostscript['product'] = revision.product
					ghostscript['revision'] = revision.revision
					ghostscript['revisiondate'] = revision.revisiondate
					ghostscript['copyright'] = revision.copyright
			except GSError as e:
				error(e)

		return {
			'ghostscript': ghostscript,
			'jobs': JOBS,
			'start': START.isoformat()
		}

@app.get('/printers')
async def printers():
	printers: List[dict] = []

	result: CompletedProcess = run(['wmic', 'printer', 'list', 'full'], encoding='GBK', text=True, capture_output=True)
	output: str = sub(r'\s*\n\s*', '\n', result.stdout).strip()
	with StringIO(output) as f:
		printer: dict = None
		for line in f.readlines():
			try:
				[ key, value ] = line.split('=', 1)
				if key.casefold() == 'Comment'.casefold():
					if printer:
						printers.append(printer)
					printer = {
						'comment': value.strip()
					}
				elif key.casefold() == 'Default'.casefold():
					printer['default'] = 'TRUE'.casefold() == value.strip().casefold()
				elif key.casefold() == 'Description'.casefold():
					printer['description'] = value.strip()
				elif key.casefold() == 'Name'.casefold():
					printer['name'] = value.strip()
			except ValueError:
				continue
		if printer:
			printers.append(printer)
	return printers

@app.put('/settings')
async def settings(body: Dict[str, str]):
	dll: str = body.get('dll')
	with Config() as config:
		config.set('Ghostscript', 'dll', dll)
	return Response(content=None, status_code=HTTP_204_NO_CONTENT)

@app.post('/printer/{printer}/job')
async def job(printer: str, body: Union[Dict[str, str], bytes]):
	info('Submitting job to printer [%s]', printer)
	filename: str = None
	try:
		if isinstance(body, dict):
			url: str = body.get('url')
			info('Fetching from URL %s', url)
			response = urlopen(url, timeout=10, context=CONTEXT)
			body = response.read()
		info('File size: %s', len(body))

		with NamedTemporaryFile('bw', suffix='.pdf', delete=False) as tmpfile:
			filename = tmpfile.name
			#print(f'Creating temp file {filename}')
			tmpfile.write(body)

		with GS(printer) as gs:
			gs.print(filename)

		JOBS['success'] = JOBS.get('success', 0) + 1
		return Response(content=None, status_code=HTTP_201_CREATED)
	except URLError as e:
		error(e)
		return Response(content=None, status_code=HTTP_502_BAD_GATEWAY)
	except GSError as e:
		error(e)
		return Response(content=None, status_code=HTTP_503_SERVICE_UNAVAILABLE)
	except Exception as e:
		error(e)
		return Response(content=None, status_code=HTTP_500_INTERNAL_SERVER_ERROR)
	finally:
		if filename:
			try:
				unlink(filename)
				info('Removed temp file %s', filename)
			except:
				exception('Failed to remove file')
				pass
		JOBS['total'] = JOBS.get('total', 0) + 1
		JOBS['failure'] = JOBS.get('total') - JOBS.get('success', 0)

'''
Attributes=68
Availability=
AvailableJobSheets=
AveragePagesPerMinute=0
Capabilities={4,2,5}
CapabilityDescriptions={"Copies","Color","Collate"}
Caption=GP-L80180 Series
CharSetsSupported=
Comment=爱的色放
ConfigManagerErrorCode=
ConfigManagerUserConfig=
CurrentCapabilities=
CurrentCharSet=
CurrentLanguage=
CurrentMimeType=
CurrentNaturalLanguage=
CurrentPaperType=
Default=TRUE
DefaultCapabilities=
DefaultCopies=
DefaultLanguage=
DefaultMimeType=
DefaultNumberUp=
DefaultPaperType=
DefaultPriority=0
Description=
DetectedErrorState=0
DeviceID=GP-L80180 Series
Direct=FALSE
DoCompleteFirst=FALSE
DriverName=GP-L80180 Series
EnableBIDI=FALSE
EnableDevQueryPrint=FALSE
ErrorCleared=
ErrorDescription=
ErrorInformation=
ExtendedDetectedErrorState=0
ExtendedPrinterStatus=2
Hidden=FALSE
HorizontalResolution=203
InstallDate=
JobCountSinceLastReset=0
KeepPrintedJobs=FALSE
LanguagesSupported=
LastErrorCode=
Local=TRUE
Location=
MarkingTechnology=
MaxCopies=
MaxNumberUp=
MaxSizeSupported=
MimeTypesSupported=
Name=GP-L80180 Series
PaperSizesSupported={1,1,1}
PNPDeviceID=
PortName=USB001
PowerManagementCapabilities=
PowerManagementSupported=
PrinterPaperNames={"Gprinter80(72) x 210 mm","Gprinter80(72) x 297 mm","Gprinter80(72) x 3276 mm"}
PrinterState=0
PrinterStatus=3
PrintJobDataType=RAW
PrintProcessor=winprint
SeparatorFile=
ServerName=
ShareName=
SpoolEnabled=TRUE
StartTime=
Status=Unknown
StatusInfo=
SystemName=DAJINGXI
TimeOfLastReset=
UntilTime=
VerticalResolution=203
'''
